package vcp.client.GUI.mvc.model;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

import ocsf.client.AbstractClient;

import org.joda.time.DateTime;

import vcp.client.GUI.mvc.controller.UIRootController;
import vcp.client.exceptions.EmptyRequestException;
import vcp.client.interfaces.IObservable;
import vcp.client.interfaces.IObserver;
import vcp.client.interfaces.delegates.IControllerModelDelegate;
import vcp.client.interfaces.listeners.IConnectionListener;
import vcp.client.request.Request;
import vcp.client.request.UserLogoutRequest;
import vcp.client.request.response.ResponseRefresh;
import vcp.client.request.response.ResponseRequest;
import vcp.client.request.response.ResponseTimeChange;
import vcp.client.utility.Common.UserNotification;
import vcp.client.utility.UtilityHelper;
import vcp.enums.PopUpMessageType;

/**
 * 
 * @author VCP team
 *         <p>
 *         root model extend from IObservable
 *         </p>
 * @see IObservable
 */
public class UIRootModel extends AbstractClient implements IObservable
{
	/** controller delegate to communicate with **/
	private IControllerModelDelegate controllerDelegate;

	public void setControllerDelegate(IControllerModelDelegate controllerDelegate)
	{
		this.controllerDelegate = controllerDelegate;
	}

	/** connection listener **/
	private IConnectionListener connectionListener;

	public void setConnectionListener(IConnectionListener connectionListener)
	{
		this.connectionListener = connectionListener;
	}

	/** array of screens **/
	private final ArrayList<IObserver> screensWaitingForRefresh;
	/** HashMap of requests **/
	private final HashMap<Long, IObserver> requestTracker;
	/** request number **/
	private long request;

	/**
	 * fill the array and HashMap with information
	 */
	public UIRootModel()
	{
		super(null, 0);
		this.requestTracker = new HashMap<>();
		this.screensWaitingForRefresh = new ArrayList<>();
		this.request = 0;
	}

	/**
	 * check if connected
	 */
	public Boolean connectToServer(String host, int port)
	{
		boolean connected = true;
		try
		{
			host = UIRootController.DEBUG ? "localhost" : host;
			this.setHost(host);
			this.setPort(port);

			this.openConnection();
		}
		catch (IOException e)
		{
			connected = false;
		}
		return connected;
	}

	/**
	 * disconnect from the server
	 */
	public void disconnectFromServer()
	{
		try
		{
			this.sendToServer(new UserLogoutRequest());
			this.closeConnection();
		}
		catch (IOException e)
		{
		}
	}

	/****************************************************************************************************************************
	 *** AbstractClient methods *** //
	 ****************************************************************************************************************************/

	@Override
	protected synchronized void handleMessageFromServer(Object msg)
	{
		if (msg instanceof ResponseRequest)
		{
			ResponseRequest response = (ResponseRequest) msg;
			long requestID = response.getResponseID();

			if (response.isSucceded())
			{
				Object data = response.getData();
				if (response instanceof ResponseTimeChange)
				{
					DateTime dateTime = (DateTime) data;
					this.controllerDelegate.onTimeChange(dateTime);
				}
				else if (response instanceof ResponseRefresh)
				{
					for (IObserver observer : this.screensWaitingForRefresh)
					{
						observer.updateWithReferesh(data);
					}
				}
				else
				{
					IObserver observer = this.requestTracker.get(requestID);
					if (observer != null)
					{
						observer.update(data);
					}
				}
			}
			else
			{
				String error = response.getErrorMessage();
				this.controllerDelegate.showPopUpMessage(error, PopUpMessageType.WARNING);
				this.controllerDelegate.loadingStop();
			}

			this.requestTracker.remove(requestID);
		}
	}

	@Override
	protected void connectionException(Exception exception)
	{
		StringBuilder buildException = new StringBuilder();
		for (StackTraceElement stackTraceElement : exception.getStackTrace())
		{
			buildException.append("Class:" + stackTraceElement.getClassName());
			buildException.append(", method:" + stackTraceElement.getMethodName());
			buildException.append(", caused error on line:" + stackTraceElement.getLineNumber());
			buildException.append("\n");
		}

		this.controllerDelegate.loadingStop();
		this.connectionListener.onConnectionLost(buildException.toString());
	}

	/****************************************************************************************************************************
	 *** IObservable methods *** //
	 ****************************************************************************************************************************/

	@Override
	public void registerAndSendRequest(Request request, IObserver observer) throws EmptyRequestException, IOException
	{
		if (UtilityHelper.getUtilityHelperInstance().isParamsNotNull(request, observer))
		{
			this.requestTracker.put(this.request, observer);
			request.setRequestID(this.request);
			this.request++;

			this.sendToServer(request);
		}
		else
		{
			throw new EmptyRequestException(UserNotification.CANNOT_PERFORM_REQUEST);
		}
	}

	@Override
	public void registerForUpdates(IObserver observer)
	{
		if (observer == null)
		{
			this.screensWaitingForRefresh.clear();
		}
		else
		{
			if (!this.screensWaitingForRefresh.contains(observer))
			{
				this.screensWaitingForRefresh.add(observer);
			}
		}
	}
}
